﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BoneImp {
	public class SkeletonBase {
		public SkeletonData data;

		int boneCount;
		public Bone[] bones;
		public Bone Root;

		int slotCount;
		public Slot[] slots;
		Slot[] drawOrder;

		Skin skin;
		float r, g, b, a;
		public float time;
		internal bool flipX, flipY;

		#region Properties
		public bool FlipX {
			get {
				return flipX;
			}
			set {
				flipX = value;
				updateWorldTransform();
			}
		}
		public bool FlipY {
			get {
				return flipY;
			}
			set {
				flipY = value;
				updateWorldTransform();
			}
		}

		public float X {
			get {
				return Root.x;
			}
			set {
				Root.x = value;
				updateWorldTransform();
			}
		}

		public float Y {
			get {
				return Root.y;
			}
			set {
				Root.y = value;
				updateWorldTransform();
			}
		}
		#endregion

		public SkeletonBase(SkeletonData data){
			this.data = data;
			this.boneCount = this.data.bones.Count;
			this.bones = new Bone[this.boneCount];
			int i, ii;
			for (i = 0; i < this.boneCount; ++i) {
				BoneData boneData = this.data.bones[i];
				Bone parent = null;
				if (boneData.parent != null) {
					/* Find parent bone. */
					for (ii = 0; ii < this.boneCount; ++ii) {
						if (data.bones[ii] == boneData.parent) {
							parent = this.bones[ii];
							break;
						}
					}
				}
				this.bones[i] = new Bone(boneData, parent);
			}
			this.Root = this.bones[0];

			this.slotCount = data.slots.Count;
			this.slots = new Slot[this.slotCount];
			for (i = 0; i < this.slotCount; ++i) {
				SlotData slotData = data.slots[i];

				/* Find bone for the slotData's boneData. */
				Bone bone = null;
				for (ii = 0; ii < this.boneCount; ++ii) {
					if (data.bones[ii] == slotData.boneData) {
						bone = this.bones[ii];
						break;
					}
				}

				this.slots[i] = new Slot(slotData, this, bone);
			}

			this.drawOrder = new Slot[this.slotCount];
			for (int k = 0; k < this.slotCount; k++){
				this.drawOrder[k] = this.slots[k];
			}

			this.r = 1;
			this.g = 1;
			this.b = 1;
			this.a = 1;
		}

		internal void updateWorldTransform() {
			for (int i = 0; i < this.boneCount; ++i)
				this.bones[i].updateWorldTransform(this.flipX, this.flipY);
		}

		internal void setToBindPose() {
			setBonesToBindPose();
			setSlotsToBindPose();
		}

		void setBonesToBindPose() {
			for (int i = 0; i < this.boneCount; ++i)
				this.bones[i].setToBindPose();
		}

		void setSlotsToBindPose() {
			for (int i = 0; i < this.slotCount; ++i)
				this.slots[i].setToBindPose();
		}

		/* Returns 0 if the bone was not found. */
		Bone findBone(string boneName) {
			int i;
			for (i = 0; i < this.boneCount; ++i)
				if (this.data.bones[i].name == boneName) return this.bones[i];
			return null;
		}
		/* Returns -1 if the bone was not found. */
		int findBoneIndex(string boneName) {
			int i;
			for (i = 0; i < this.boneCount; ++i)
				if (this.data.bones[i].name == boneName) return i;
			return -1;
		}

		/* Returns 0 if the slot was not found. */
		Slot findSlot(string slotName) {
			int i;
			for (i = 0; i < this.slotCount; ++i)
				if (this.data.slots[i].name == slotName) return this.slots[i];
			return null;
		}
		/* Returns -1 if the slot was not found. */
		int findSlotIndex(string slotName) {
			int i;
			for (i = 0; i < this.slotCount; ++i)
				if (this.data.slots[i].name == slotName) return i;
			return -1;
		}

		/* Sets the skin used to look up attachments not found in the SkeletonData defaultSkin. Attachments from the new skin are
		 * attached if the corresponding attachment from the old skin was attached.
		 * @param skin May be null.*/
		void setSkin(Skin newSkin) {
			if (this.skin != null && newSkin != null) newSkin.AttachAll(this, this.skin);
			this.skin = newSkin;
		}

		/* Returns false if the skin was not found. See Skeleton_setSkin.
		 * @param skinName May be null. */
		public bool SetSkinByName(string skinName) {
			if (skinName == null) {
				this.setSkin(null);
				return true;
			}
			Skin skin = this.data.findSkin(skinName);
			if (skin == null) return false;
			this.setSkin(skin);
			skin.AttachAll(this);
			return true;
		}

		/* Returns 0 if the slot or attachment was not found. */
		Attachment getAttachmentForSlotName(string slotName, string attachmentName) {
			int slotIndex = this.data.findSlotIndex(slotName);
			return getAttachmentForSlotIndex(slotIndex, attachmentName);
		}

		/* Returns 0 if the slot or attachment was not found. */
		public Attachment getAttachmentForSlotIndex(int slotIndex, string attachmentName) {
			if (slotIndex == -1) return null;
			if (this.skin != null) {
				Attachment attachment = this.skin.getAttachment(slotIndex, attachmentName);
				if (attachment != null) return attachment;
			}
			if (this.data.defaultSkin != null) {
				Attachment attachment = this.data.defaultSkin.getAttachment(slotIndex, attachmentName);
				if (attachment != null) return attachment;
			}
			return null;
		}

		/* Returns 0 if the slot or attachment was not found. */
		int setAttachment(string slotName, string attachmentName) {
			int i;
			for (i = 0; i < this.slotCount; ++i) {
				Slot slot = this.slots[i];
				if (slot.data.name == slotName) {
					Attachment attachment = this.getAttachmentForSlotIndex(i, attachmentName);
					if (attachment == null) return 0;
					slot.setAttachment(attachment);
					return 1;
				}
			}
			return 0;
		}

		public virtual void update(float deltaTime) {
			this.time += deltaTime;
		}
	}
}
